Slide Puzzle

Capturing Sprites Within the Program - Novice Level

© 2004, Janet Terra

author contact:

janetterra@yahoo.com

Home

Tip Corner: Using

Game Programming

Round Buttons

Rotating Objects

LBW: Book Marks

Slide Puzzle

Speech DLL

Addresses

Beginners Programming

Newsletter help

Index


Number Slide Puzzle: Capturing Sprites Within the Program

Sometimes a graphic design is so simple that it doesn't make sense to clutter the hard drive with numerous bitmaps. In this program, the commands for the tiles remain constant. It is just the number that changes for each tile. Rather than include 24 similar sprites in a zip file, the sprites used for this newsletter's Number Slide Puzzle are created and captured within the program itself. The simple tiles are drawn twice, the first time for the desired color, the second time for the mask. The rules for the mask are

  1. Any black object (including the necessary black background) needs to be white
  2. Any non-black object needs to be black

For more detailed information on sprites and masks, check these resources:

  1. the Liberty BASIC help file
  2. maskmaker.bas (by Alyce Watson), included with Liberty BASIC
  3. Mastering Liberty Basic © by Alyce Watson (in particular, see the animation demo "Boy Meets Girl")

Using shades of gray to outline the tiles adds texture to each tile and gives a more professional look; likewise, the multicolored border.

The program starts by first drawing and capturing the required tiles, then drawing the puzzle frame and setting that frame as the background for the sprites. The comments are fairly descriptive as to what each step of coding achieves, so it isn't necessary to reiterate here. Notice that there are no "flush" commands within the program. I'm finding it difficult to determine when to use the "flush" command when sprites are being used. Since the "drawsprites" command causes all "spritevisible spritename on" to be displayed, and a "drawsprites" command is issued with each recognized left mouse click, then it's easy for the user to just left click and redraw the screen that may have been inadvertently erased.

The algorithm for checking to see if each tile is in order consists of four parts.


'Check Part One: (column four, row four)
 does tile (4,4) = 0 (i.e., no tile there)?
'Check Part Two: (rows zero through three)
is the tile in column one 1 greater than the tile in column zero?
is the tile in column two 1 greater than the tile in column one?
is the tile in column three 1 greater than the tile in column two?
is the tile in column four 1 greater than the tile in column three?
… since row four (bottom row) only has four tiles (one space must remain empty) then,
'Check Part Three: (row four)
is the tile in column one 1 greater than the tile in column zero?
is the tile in column two 1 greater than the tile in column one?
is the tile in column three 1 greater than the tile in column two?
'Check Part Four: (rows one through four)
is the tile in row one column one 1 greater than the tile in row zero column four?
is the tile in row two column one 1 greater than the tile in row one column four?
is the tile in row three column one 1 greater than the tile in row two column four?
is the rile in row four column one 1 greater than the tile in row three column four?

Any 'no' answer causes the program to return to the 'wait state. If all these conditions are met, then the tiles are in consecutive order and the puzzle is solved. This coding is piecework at best, but it works without crashing and is simple enough to follow. As Tom Nally said, "[it may be best to] dissemble the long lines of code into shorter lines of code, even if it would result in a slower-executing application. In other words . . . deliberately making the code less efficient, with the hope of establishing order and clarity." (Tips For the Hobbyist Programmer, Newsletter #116). Words to live by, Tom! Thanks for the validation.

Two simple graphic commands are then used to reward the puzzle solver. The first is randomly issued sprite orientation commands. Notice that at the end of the loop, each sprite is returned to its normal position. The second is the use of print #handle, "rule rulename" to achieve the flashing effect for "You Did It!" This graphic flashing was inspired by Gordon Rahman (Liberty BASIC Forum: Liberty BASIC Wishlist: Round buttons). Again, for a more detailed explanation of "rule rulename", consult the Liberty Basic Help File and also Liberty BASIC Newsletter #101 (thanks to Alyce again and again).

There was a short burst of conversation regarding sound recently in the Yahoo!Liberty Groups message board. Stefan Pendl (Yahoo! Groups: libertybasic Message #18574) offered some code to achieve sound without the use of .wav files. It seems that different versions of Windows (9x, 2000, NT, ME, XP, etc.) are not all compatible with sound coding, so any ideas of auditory reward were abandoned.

You may want to use the spritemovexy command with a timer delay rather than the spritexy command to "see" the tile "slide" into the empty spot. Be careful, though. If the last tile hasn't completely slid into its new position before the player left clicks for the next move, the x and y coordinates can become less than perfect. If you choose to do this, you may want to flag the spriteMoveXY loop to prevent leftmouseclick action until the loop is completed. Happy puzzling!


'Code follows
'------------------------------------------------------------------------------------------------

''''''''''''''''''''''''''''''''''''
' Slide Puzzle                     '
' by Janet Terra                   '
' janetterra@yahoo.com             '
''''''''''''''''''''''''''''''''''''
' Made with Liberty BASIC v3.03    '
'    by Carl Gundel                '
''''''''''''''''''''''''''''''''''''
'slidepuzzle.bas
    nomainwin

'Define the Dimensions of the Window
    WindowWidth = 304
    WindowHeight = 352

'Define the Menu
    menu #w, "&Options","&New Puzzle",[newPuzzle], _
        "&Same Puzzle",[samePuzzle],|, _
        "&Quit",[endProgram]

'Define the Graphic Box
    graphicbox #w.g, 5, 5, 286, 286

'Open the Graphic Boxes in the Program's Main Window
    open "Slide Puzzle" for window as #w

'Event Controls
    #w, "trapclose [endProgram]"
    #w.g, "when leftButtonUp [slideTile]"
    #w.g, "setfocus"

'Define the Variables
    dim tile$(24) '24 slide sprites
    dim ord(24) 'prevents repeat numbers
    dim dsrd(25) 'shuffled slide positions 1 - 25
    empty = 0 'random empty position
    dim tile(4,4) 'numbers placed in 5x5 grid

'The Slide Sprites
    #w.g, "down"
    #w.g, "fill white"
    for n = 1 to 24
    tile$ = "tile"
    if n < 10 then
    tile$=tile$;"0"
    end if
    tile$=tile$;str$(n)
    call drawTile n, tile$
    tile$(n)=tile$
    next n

'Draw the Grid
    #w.g, "cls"
    #w.g, "fill black"
    #w.g, "size 2"
    #w.g, "color black"
    #w.g, "backcolor brown"
    #w.g, "place 1 1"
    #w.g, "boxfilled 284 284"
    #w.g, "place 6 6"
    #w.g, "color darkpink"
    #w.g, "backcolor darkpink"
    #w.g, "boxfilled 279 279"
    #w.g, "place 11 11"
    #w.g, "color darkgreen"
    #w.g, "backcolor darkgreen"
    #w.g, "boxfilled 274 274"
    #w.g, "color black"
    #w.g, "backcolor lightgray"
    #w.g, "place 16 16"
    #w.g, "boxfilled 269 269"

'Set the Grid as Background
    #w.g, "getbmp grid 0 0 284 284"
    #w.g, "background grid"
    #w.g, "flush"
    #w.g, "discard"

    [newPuzzle]
'Shuffle the Tiles
    [orderedRandom]
    for i = 0 to 24
    ord(i) = i
    next i
    for i = 1 to 24
        [ChooseNumber]
        rpt = 0
        n = int(rnd(1)*24)+1
        for j = 1 to i
        if ord(n)=0 then
        rpt = 1
        j = i
        end if
        next j
        if rpt = 1 then [ChooseNumber]
        dsrd(i) = n
        ord(n)=0
    next i
'Choose One Empty Slot
    empty = int(rnd(1)*24)+1
    dsrd(25)=dsrd(empty)
    dsrd(empty)=0

    [samePuzzle]
'Assigning Tiles to Grid
    ct = 1
    for row = 0 to 4
    for col = 0 to 4
    tile(col,row)=dsrd(ct)
    ct = ct+1
    next col
    next row

'Place the Tiles
    y = 17
    for row = 0 to 4
    x = 17
    for col = 0 to 4
    if tile(col,row)<>0 then
    #w.g, "spritexy ";tile$(tile(col,row));" ";x;" ";y
    #w.g, "spritevisible ";tile$(tile(col,row));" on"
    end if
    x = x + 50
    next col
    x = 17:y = y + 50
    next row
    #w.g, "drawsprites"
    wait

'Slide Tile
    [slideTile]
'Step One: Which Tile to Move
    x = MouseX
    y = MouseY
    if x < 16 or x > 266 then
    wait
    end if
    if y < 16 or y > 266 then
    wait
    end if
    col = int((x-17)/50)
    row = int((y-17)/50)

'Step Two: Is It a Valid Tile?
    if tile(col,row) = 0 then
    msg$ = "Please Choose a Valid Tile"
    call message msg$
    tm = 500
    gosub [timerDelay]
    wait
    end if
'Step Three: Which Direction to Move
    dir = 0
    if row > 0 then
        if tile(col,row-1) = 0 then
            dir = 1
        end if
    end if
    if col < 4 then
        if tile(col+1,row) = 0 then
            dir = 2
        end if
    end if
    if row < 4 then
        if tile(col,row+1) = 0 then
            dir = 3
        end if
    end if
    if col > 0 then
        if tile(col-1,row) = 0 then
            dir = 4
        end if
    end if

'Step Four: Is that Slot Open?
    if dir = 0 then
    msg$ = "Please Choose a Valid Tile"
    call message msg$
    tm = 500
    gosub [timerDelay]
    wait
    end if

'Step Five: Move Tile to New Slot
    x = 17+col*50
    y = 17+row*50
    tile$ = tile$(tile(col,row))
    select case dir
    case 1
    tile(col,row-1)=tile(col,row)
    y = y-50
    case 2
    tile(col+1,row)=tile(col,row)
    x = x+50
    case 3
    tile(col,row+1)=tile(col,row)
    y = y+50
    case 4
    tile(col-1,row)=tile(col,row)
    x = x-50
    end select
    #w.g, "spritexy ";tile$;" ";x;" ";y
    tile(col,row)=0
    #w.g, "drawsprites"

'Step Six: Are the Tiles In Order?
    if tile(4,4) <> 0 then
    wait
    end if
    dsrd = 0
    for row = 0 to 3
    for col = 0 to 3
    if tile(col+1,row) <> tile(col,row)+1 then
    dsrd = 1
    end if
    next col
    next row
    for col = 0 to 2
    if tile(col+1,row) <> tile(col,row)+1 then
    dsrd = 1
    end if
    next col
    for row = 0 to 3
    if tile(0,row+1) <> tile(4,row)+1 then
    dsrd = 1
    end if
    next row

'If No Tiles Out of Order (dsrd = 0)
    if dsrd = 0 then

'Visual Reward for Ordered Tiles
    'Tiles Rotate
    msg$ = "You Did It!"
    for display = 1 to 10
    for tile = 1 to 24
    show = int(rnd(1)*4)+1
    tile$ = tile$(tile)
    select case show
    case 1
    #w.g, "spriteorient ";tile$;" normal"
    case 2
    #w.g, "spriteorient ";tile$;" mirror"
    case 3
    #w.g, "spriteorient ";tile$;" flip"
    case 4
    #w.g, "spriteorient ";tile$;" rotate180"
    end select
    next tile
    #w.g, "drawsprites"
    tm = 10
    gosub [timerDelay]
    #w.g, "drawsprites"
    next display
    for tile = 1 to 24
    tile$ = tile$(tile)
    #w.g, "spriteorient ";tile$;" normal"
    next tile
    #w.g, "drawsprites"
    'You Win Flashes
    for i = 1 to 3
    for j = 1 to 9
    select case j
    case 1
    #w.g, "rule "; _R2_MERGEPENNOT
    R2$ = "_R2_XORPEN"
    case 2
    #w.g, "rule ";_R2_MERGEPEN
    R2$ = "_R2_MERGEPEN"
    case 3
    #w.g, "rule "; _R2_NOTCOPYPEN
    R2$ = "_R2_NOTCOPYPEN"
    case 4
    #w.g, "rule "; _R2_NOT
    R2$ = "_R2_NOT"
    case 5
    #w.g, "rule "; _R2_MASKPENNOT
    R2$ = "_R2_MASKPENNOT"
    case 6
    #w.g, "rule "; _R2_NOTMERGEPEN
    R2$ = "_R2_NOTMERGEPEN"
    case 7
    #w.g, "rule "; _R2_NOTMASKPEN
    R2$ = "_R2_NOTMASKPEN"
    case 8
    #w.g, "rule "; _R2_XORPEN
    R2$ = "_R2_XORPEN"
    case 9
    #w.g, "rule "; _R2_NOTXORPEN
    R2$ = "_R2_NOTXORPEN"
    end select
    call message msg$
    #w.g, "color darkpink"
    #w.g, "backcolor green"
    #w.g, "size 5"
    #w.g, "place 105 132"
    #w.g, "boxfilled 186 157"
    tm = 50
    gosub [timerDelay]
    next j
    next i
    #w.g, "rule "; _R2_COPYPEN
    #w.g, "drawsprites"
    call message msg$
    #w.g, "color darkpink"
    #w.g, "size 5"
    #w.g, "place 105 132"
    #w.g, "box 186 157"
    end if

'Step Seven: Await Next Command
    wait

    sub message msg$
    #w.g, "color yellow"
    #w.g, "backcolor brown"
    #w.g, "font times_new_roman 12 bold"
    #w.g, "stringwidth? msg$ width"
    #w.g, "place ";144-width/2;" 150"
    #w.g, "\";msg$
    end sub

    [timerDelay]
    timer tm, [delay]
    wait
    [delay]
    timer 0
    #w.g, "drawsprites"
    return

    sub drawTile n, tile$
    n$ = str$(n)
    #w.g, "size 5"
    #w.g, "color black"
    #w.g, "backcolor black"
    #w.g, "place 1 1"
    #w.g, "boxfilled 50 50"
    #w.g, "color darkgray"
    #w.g, "backcolor darkblue"
    #w.g, "size 5"
    #w.g, "place 1 51"
    #w.g, "boxfilled 50 100"
    #w.g, "color darkblue"
    #w.g, "size 1"
    #w.g, "box 50 99"
    #w.g, "color darkgray"
    #w.g, "font times_new_roman 24 bold"
    #w.g, "stringwidth? n$ width"
    #w.g, "place ";25-width/2;" ";85
    #w.g, "\";n
    #w.g, "getbmp  ";tile$;" 1 1 50 100"
    #w.g, "addsprite ";tile$;" ";tile$
    #w.g, "spritevisible ";tile$;" off"
    end sub

    [endProgram]
    confirm "Quit this program? ";ans$
    if ans$ <> "yes" then
    wait
    end if
    close #w
    for i = 1 to 24
    unloadbmp tile$(i)
    next i
    unloadbmp "grid"
    end

'Code ends
'------------------------------------------------------------------------------------------------


Home

Tip Corner: Using

Game Programming

Round Buttons

Rotating Objects

LBW: Book Marks

Slide Puzzle

Speech DLL

Addresses

Beginners Programming

Newsletter help

Index